
package uestc.bitman.edxapp.downloading;

import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Message;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URL;
import java.net.URLConnection;

public class DownloadTaskThread {

    public static final int MSG_DOWNLOAD_START = 1;

    public static final int MSG_DOWNLOAD_END = 2;

    public static final int MSG_DOWNLOAD_PROGRESS = 3;

    public static final int MSG_DOWNLOAD_FAILURE = 4;

    public static final int MSG_DOWNLOAD_FINISH = 5;

    public static final int MSG_DOWNLOAD_THREAD_EXCEPTION = 6;

    private int mDownloadSize = 0;

    private int mFileSize = 0;

    private int mDownloadThreadNum = 0;

    private int mSpeed = 0;

    private IDownloadingCallback mCallback = null;

    private DownloadTask mDownloadingTask = null;

    public boolean mStop = false;

    public FileDownloadThread[] mThreads = null;

    public boolean hasBreakPoints = false;

    private int mThreadsErrorCount = 0;

    private String mDownloadUrl;

    private String mDirPath;

    private String mFilePath;

    public DownloadTaskThread(String url, String filePath, int threadNum) {
        this.mDownloadUrl = url;
        this.mFilePath = filePath;
        this.mDirPath = filePath.substring(0, filePath.lastIndexOf('/'));
        this.mDownloadThreadNum = threadNum <= 0 ? 1 : threadNum;
        mThreads = new FileDownloadThread[threadNum];
        for (int i = 0; i < threadNum; i++) {
            mThreads[i] = new FileDownloadThread();
            mThreads[i].setName("thread" + i);
        }
    }

    public DownloadTaskThread() {

    }

    public void cancelDownload() {
        mStop = true;
        File file = new File(mFilePath);
        file.delete();
    }

    public void stopDownload() {
        mStop = true;
    }

    public int getThreadsNum() {
        return mDownloadThreadNum;
    }

    public void setTaskCallback(IDownloadingCallback callback) {
        this.mCallback = callback;
    }

    public String getFileName() {
        return mFilePath.substring(mFilePath.lastIndexOf("/") + 1, mFilePath.length());
    }

    public String getTargetUrl() {
        return mDownloadUrl;
    }

    public DownloadingTaskBreakPoints getBreakPoints() {
        DownloadingTaskBreakPoints breakPoints = new DownloadingTaskBreakPoints(mDownloadUrl,
                mFilePath, mDownloadThreadNum);
        int i;
        for (i = 0; i < mDownloadThreadNum; i++) {
            breakPoints.setBreakPoint(i, mThreads[i].startPostion, mThreads[i].downloadSize,
                    mThreads[i].curruentPosition, mThreads[i].endPosition);
        }
        return breakPoints;
    }

    public void startDownload() {
        mStop = false;
        File file = new File(mDirPath);

        // create directory
        if (!file.exists()) {
            file.mkdir();
        }

        mDownloadingTask = new DownloadTask();
        mDownloadingTask.start();
    }

    public void loadBreakPoints(DownloadingTaskBreakPoints breakPoints,
            IDownloadingCallback callback) {
        this.mCallback = callback;
        this.mDownloadUrl = breakPoints.urlString;
        this.mDownloadThreadNum = breakPoints.threadsNum;
        this.mFilePath = breakPoints.pathString;
        this.mDirPath = mFilePath.substring(0, mFilePath.lastIndexOf('/'));
        mThreads = new FileDownloadThread[breakPoints.threadsNum];
        for (int i = 0; i < mDownloadThreadNum; i++) {
            mThreads[i] = new FileDownloadThread();
            mThreads[i].initBreakPoint(breakPoints.start[i], breakPoints.size[i],
                    breakPoints.current[i], breakPoints.end[i]);
        }
        hasBreakPoints = true;
    }

    @SuppressLint("HandlerLeak")
    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_DOWNLOAD_PROGRESS: {
                    int progress = 0;
                    progress = (Double.valueOf((mDownloadSize * 1.0 / mFileSize * 100))).intValue();
                    if (mCallback != null) {
                        mCallback.onProgress(progress, mSpeed);
                    }
                }
                    break;
                case MSG_DOWNLOAD_START:
                    if (mCallback != null) {
                        mCallback.onDownloadStart(mFileSize);
                    }
                    break;
                case MSG_DOWNLOAD_END: {
                    if (mCallback != null) {
                        int progress = 0;
                        progress = (Double.valueOf((mDownloadSize * 1.0 / mFileSize * 100)))
                                .intValue();
                        mCallback.onDownloadEnd(progress);
                    }
                }
                    break;
                case MSG_DOWNLOAD_FAILURE:
                    if (mCallback != null) {
                        mCallback.onNoConnectionError(0);
                    }
                    break;
                case MSG_DOWNLOAD_THREAD_EXCEPTION: {
                    mThreadsErrorCount++;
                    if (mCallback != null) {
                        if (mThreadsErrorCount == mDownloadThreadNum) {
                            mCallback.onDownloadInterrupted(0);
                        }

                    }
                }
                    break;
            }
        }
    };

    public class DownloadTask extends Thread {

        private int blockSize, downloadSizeMore;

        public DownloadTask() {
            blockSize = downloadSizeMore = 0;
        }

        public void run() {
            int i;
            try {
                URL url = new URL(mDownloadUrl);
                URLConnection conn = url.openConnection();
                // get the file size,if the fileSize == -1,connection error.
                mFileSize = conn.getContentLength();
                if (mFileSize == -1) {
                    handler.sendEmptyMessage(MSG_DOWNLOAD_FAILURE);
                    return;
                }
                // get the count per thread
                blockSize = mFileSize / mDownloadThreadNum;
                downloadSizeMore = (mFileSize % mDownloadThreadNum);
                File file = new File(mFilePath);

                if (!hasBreakPoints) {
                    handler.sendEmptyMessage(MSG_DOWNLOAD_START);

                    for (i = 0; i < mDownloadThreadNum - 1; i++) {
                        mThreads[i].init(url, file, i * blockSize, (i + 1) * blockSize - 1);
                        mThreads[i].start();
                    }
                    mThreads[i].init(url, file, i * blockSize, (i + 1) * blockSize - 1
                            + downloadSizeMore);
                    mThreads[i].start();

                } else {
                    handler.sendEmptyMessage(MSG_DOWNLOAD_START);
                    for (i = 0; i < mDownloadThreadNum; i++) {
                        mThreads[i].setURL(url);
                        mThreads[i].setFile(file);
                        mThreads[i].start();
                    }
                }

                boolean finished = false;
                while (!finished && !mStop) {
                    int last = mDownloadSize;
                    mDownloadSize = downloadSizeMore;
                    finished = true;
                    for (i = 0; i < mThreads.length; i++) {
                        mDownloadSize += mThreads[i].getDownloadSize();
                        if (!mThreads[i].isFinished()) {
                            finished = false;
                        }
                    }
                    // every time we update the progress,notify the callback to
                    // take some actions
                    mSpeed = mDownloadSize - last;
                    handler.sendEmptyMessage(MSG_DOWNLOAD_PROGRESS);
                    sleep(1000);
                }
                for (i = 0; i < mThreads.length; i++) {
                    mThreads[i].stopThread();
                }
                handler.sendEmptyMessage(MSG_DOWNLOAD_END);
            } catch (Exception e) {
                handler.sendEmptyMessage(MSG_DOWNLOAD_FAILURE);
                e.printStackTrace();
            }
        }
    }

    class FileDownloadThread extends Thread {

        private static final int BUFFER_SIZE = 1024;

        public int startPostion;

        public int endPosition;

        public int curruentPosition;

        public int downloadSize = 0;

        private URL url;

        private File file;

        boolean stop = false;

        // current thread finish downloading
        private boolean finished = false;

        private Handler exceptionHandler = null;

        public FileDownloadThread() {
            exceptionHandler = null;
        }

        public FileDownloadThread(URL url, File file, int startPosition, int endPosition) {
            this.url = url;
            this.file = file;
            this.startPostion = startPosition;
            this.curruentPosition = startPosition;
            this.endPosition = endPosition;
            exceptionHandler = null;
        }

        public void setExceptionHandler(Handler handler) {
            exceptionHandler = handler;
        }

        public void setFile(File file) {
            this.file = file;
        }

        public void setURL(URL url) {
            this.url = url;
        }

        public void stopThread() {
            stop = true;
        }

        public void init(URL url, File file, int startPosition, int endPosition) {
            this.url = url;
            this.file = file;
            this.startPostion = startPosition;
            this.curruentPosition = startPosition;
            this.endPosition = endPosition;
        }

        public void initBreakPoint(int start, int size, int current, int end) {
            this.startPostion = start;
            this.curruentPosition = current;
            this.endPosition = end;
            this.downloadSize = size;
        }

        public void run() {
            BufferedInputStream bis = null;
            RandomAccessFile fos = null;
            byte[] buf = new byte[BUFFER_SIZE];
            URLConnection con = null;
            try {
                con = url.openConnection();
                con.setAllowUserInteraction(true);
                // the start and end postion of current thread
                con.setRequestProperty("Range", "bytes=" + curruentPosition + "-" + endPosition);
                // R/W using RandomAccessFile
                fos = new RandomAccessFile(file, "rw");
                fos.seek(curruentPosition);
                bis = new BufferedInputStream(con.getInputStream());
                // R/W file with stream
                while (curruentPosition < endPosition && !stop) {
                    int len = bis.read(buf, 0, BUFFER_SIZE);
                    if (len == -1) {
                        break;
                    }
                    fos.write(buf, 0, len);
                    curruentPosition += len;
                    if (curruentPosition > endPosition) {
                        downloadSize += len - (curruentPosition - endPosition) + 1;
                    } else {
                        downloadSize += len;
                    }
                }
                this.finished = true;
                bis.close();
                fos.close();
            } catch (IOException e) {
                if (exceptionHandler != null) {
                    handler.sendEmptyMessage(MSG_DOWNLOAD_THREAD_EXCEPTION);
                }
            }
        }

        public boolean isFinished() {
            return finished;
        }

        public int getDownloadSize() {
            return downloadSize;
        }
    }
}
